/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */

package com.obixlabs.commons.xml;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Set;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

import com.obixlabs.commons.ObixException;
import com.obixlabs.commons.io.IOResourceCloser;
import com.obixlabs.commons.io.StreamReadWriteUtilities;

/**
 * <p>
 * Encapsulates an XML XSL transform process. 
 * </p>
 */
public class DOMTransform
{
	private static Logger logger = 
		LoggerFactory.getLogger(DOMTransform.class);
	
    /**
     * <p>
     * Stream from which the stylesheet to be used for the transform 
     * will be read.
     * </p>
     */
    private StreamSource xslSource;
    
    /**
     * <p>
     * Initialises an instance from the specified 
     * raw XSL bytes.
     * </p>
     * 
     * @param xsl	The XSL stylesheet (in raw byte form)
     * with which this instance will perform transforms.
     */
    public DOMTransform (byte[] xsl)
    { 
    	this(new StreamSource(  
    				new ByteArrayInputStream(xsl))); 
    }
    
    /**
     * <p>
     * Initialises an instance with a stylesheet read 
     * from the given InputStream.
     * </p>
     * 
     * @param xslStream	The stream from which to read 
     * the stylesheet which the resulting will use 
     * for performing transforms.
     * 
     */
    public DOMTransform (InputStream xslStream)
    { 
    	this (new StreamSource( 
    						StreamReadWriteUtilities.bufferStream(xslStream))); 
    }
        
    /**
     * <p>
     * Creates an instance with the specified stylesheet
     * source stream.
     * </p>
     * 
     * @param xslSource	The source stream of the stylesheet
     * to be used by this instance for performing transforms.	
     */
	public DOMTransform(StreamSource xslSource)
	{ this.xslSource = xslSource; }

    /**
     * <p>
     * Transforms the given XML document. 
     * </p>
     * 
     * @param xml	The XML document to transform.
     * @param parameters	The stylesheet parameters.
     * @return	The result of applying the instance's stylesheet
     * to the given XML document.
     * 
     * @throws ObixException	Encapsulates any error thrown 
     * by the current runtime's XML processing engine.
     */
    public synchronized byte[] transform( 	
    														Document xml,
															Map<String, Object> parameters) 
            												throws ObixException
    {
    	byte[] result;
    	
    	reset();    	
    	ByteArrayOutputStream byteResultStream=null;    	
    	try
    	{
	        byteResultStream = 
	        		new ByteArrayOutputStream();
	        StreamResult resultStream = 
	        		new StreamResult(byteResultStream);
	        
	        //initialise XML document 
	        //and XSL String
	        DOMSource sourceDocument = new DOMSource(xml);	        
	     
	        //create a transformer 
            TransformerFactory transformerFactory=
            				TransformerFactory.newInstance();            
            Transformer transformer = 
            	transformerFactory.newTransformer(xslSource);
	        
            //set parameters
            populateStyleSheetArguments(transformer, 
            							parameters);
            
            //perform transform
            transformer.transform(	sourceDocument, 
            						resultStream);
	        
            //get results from byte stream
            result = byteResultStream.toByteArray();
    	}
		catch (TransformerConfigurationException exce)
		{ throw new ObixException(exce); }
		catch (TransformerException exce)
		{ throw new ObixException(exce); }
    	finally
    	{
    		IOResourceCloser.close(byteResultStream);
    	}
    	
    	return result;
    }
    
    
    /**
     * <p>
     * This method is now deprecated, please use 
     * {@link #transform(Document, Map)} instead.
     * </p>
     * 
     * @param xml The XML to which to apply the given stylesheet.
     * @param validateXML	Indicates if the XML should be parsed with a validating 
     * parser. This applies to DTD governed documents only.
     * @param parameters	Transformer/stylesheet parameters.
     * @return The result of applying this instance's stylesheet
     * to the given XML document.
     * @throws ObixException Encapsulates any error thrown 
     * by the current runtime's XML processing engine.
     */
    @Deprecated
    public synchronized byte[] transform(   
    				byte[] xml,  boolean validateXML,
                    Map<String, Object> parameters) 
                    throws ObixException
    {
        byte[] result;
        
        reset();
    	Document xmlDocument = 
    		XMLUtilities.parseDocument(xml, validateXML);
    	result = transform(xmlDocument, parameters);
        
        return result;
    }//end method def

    /**
     * <p>
     * Resets the instance prior to the start of a new 
     * {@link #transform(Document, Map) transform}.
     * </p>
     */
    private void reset()
    {
    	try {
    		xslSource.getInputStream().reset();
    	} catch (IOException exce) {
    		logger.error("Unable to reset the InputStream on " +
    									"which transformer is built. You may experience " +
    									"problems re-using transformer instance.", 
    									exce);
    	}
    }
    
    
    /**
     * <p>
     * Populates the given Transformer instance with parameter
     * values taken from the specified map.
     * </p>
     * 
     * @param transformer	The Transformer on which the parameter values 
     * are to be set.
     * 
     * @param parameters	The parameter values with which to initialise
     * the Transformer.	
     */
    private void populateStyleSheetArguments(
    				Transformer transformer, 
    				Map<String, Object> parameters)
    {
        if (parameters!=null && !parameters.isEmpty())
        {
            Set<String> parameterKeys=
            					parameters.keySet();
            
            Object parameterValue;
            for (String parameterKey : parameterKeys)
            {
            	parameterValue = 
            		parameters.get(parameterKey);
            	transformer.setParameter(	parameterKey, 
            								parameterValue);
			} 
        }
    }	
}